/* ******************************************************************
 * This file is part of the escal project and distributed under the
 * terms of the GNU General Public License Version 3
 * Copyright (C) 2008 Epocher (xijiaosdq@gmail.com)
 * See COPYING file for licensing information.
 *******************************************************************/

#include "file_manager.h"
#include "escal.h"
#include "log_manager.h"

#include <QTimer>
#include <QTime>
#include <QTextCursor>
#include <QTextDocument>

using namespace Escal;

FileManager::FileManager(QString folderName, QString fileName, 
	QObject *parent)
{
	newFile = true;
	fileSize = 0;
	finishedSize = 0;
	acceptRange = false;
	pieceNum = 0;
	error = false;
	quit = false;
	textDocument = new QTextDocument(this);
	textCursor = new QTextCursor(textDocument);
	connect(this, SIGNAL(writeLog(QTextCursor*, QString, QString)),
		logManager, 
		SLOT(writeLogDocument(QTextCursor*, QString, QString)));
	setPaths(folderName, fileName);
};

FileManager::~FileManager()
{
	qDebug() << "FileManager::~FileManager()";
	quit = true;
	cond.wakeOne();
	wait();
	file.close();
	digestFile.close();
	if(piecesDone.count(false)==0) digestFile.remove();
}

bool FileManager::setPaths(const QString &folderName, const QString &fileName){
	this->folderName = folderName ;
	this->fileName = fileName ; 
	this->digestFileName = fileName + DigestFileSuffix;
	QTime time = QTime::currentTime();
	emit writeLog(textCursor, time.toString(), tr("set folder to: %1")
			.arg(folderName));
	time = QTime::currentTime();
	emit writeLog(textCursor, time.toString(), tr("set filename to: %1")
			.arg(fileName));
	dir.setPath(this->folderName);
	file.setFileName(dir.filePath(this->fileName));
	digestFile.setFileName(dir.filePath(this->digestFileName));
	return true;
}

void FileManager::checkDigest(){
	QMutexLocker locker(&mutex);
	if(!dir.exists()){
		emit writeLog(textCursor, QTime::currentTime().toString(),
				"Folder does not exist, trying to creat it.");
		if(!dir.mkdir(folderName)){
			emit writeLog(textCursor, QTime::currentTime().toString(),
				"Fail to Creat folder.");
		}
	}
	else{
		emit writeLog(textCursor, QTime::currentTime().toString(),
				"Folder found.");
	}
	if(!file.exists()){
		emit writeLog(textCursor, QTime::currentTime().toString(),
				"File does not exist.");

		/* if(!file.open(QIODevice::ReadWrite)){
			emit writeLog(textCursor, 
				QTime::currentTime().toString(),
				"Fail to create the File.");
		} */
		fileSize = -1;
	}
	else {
		emit writeLog(textCursor, QTime::currentTime().toString(),
				"File found.");
		file.open(QIODevice::ReadOnly);
		fileSize = file.size();
		qDebug() << fileSize;

	}
	file.close();
	digestFile.setFileName(dir.filePath(digestFileName));
	if(!digestFile.exists()){
		if(!digestFile.open(QIODevice::ReadWrite)){
			exit(1);
		} 
		digestFile.close();
		digestFile.remove();
	}
	digestFile.close();
	if(loadDigest()){
		newFile=false;
		emit fileIsInitialized();
	}
}

int FileManager::initNewFile(const qint64 &fileSize ,bool acceptRange){
	enum{INIT_OK, FILE_SIZE_ZERO, INIT_ERROR};
	qDebug() << "FileManager::initNewFile" << fileSize;
	if(fileSize<0) return INIT_ERROR;
	QMutexLocker locker(&mutex);
	if(!newFile ){
		qDebug() << "FileManager wake";
		cond.wakeOne();
		return INIT_OK;
	}
	QTime time = QTime::currentTime();
	emit writeLog(textCursor, time.toString(), tr("set filesize to: %1")
			.arg(fileSize));
	this->fileSize = fileSize;
	this->acceptRange = acceptRange;
	pieceNum = fileSize / ( PieceSize );
	if(fileSize % ( PieceSize )){
		pieceNum ++;
	}
	piecesWorking.resize(pieceNum);
	piecesDone.resize(pieceNum);
	file.resize(fileSize);
	newFile = false;
	emit fileIsInitialized();
	qDebug() << "FileManager wake";
	cond.wakeOne();
	return INIT_OK;
}

int FileManager::read(int pieceIndex, qint64 offset, int length){
	return 0;

}

int FileManager::write(int &pieceIndex, qint64 &offset, bool &gotPiece,
		const QByteArray &data)
{
	/*
	 * return zero means success.
	 */
	enum {WRITE_OK=0, NOT_GOT_PIECE, WRITE_OUT_OF_FILE};
	if(!gotPiece) return NOT_GOT_PIECE;
	WriteRequest newRequest;
	newRequest.pieceIndex = pieceIndex;
	newRequest.offset = offset;
	newRequest.data = data;
	QMutexLocker locker(&mutex);
	writeRequests << newRequest;
	offset += data.size();
	if(offset + pieceIndex * PieceSize > fileSize){
		qDebug() << "offset + pieceIndex * PieceSize > getFileSize()\n"; 
		qDebug() << "offset + pieceIndex * PieceSize: "
			 << offset + pieceIndex * PieceSize
		 	 << "\n getFileSize(): " << getFileSize();
		error = true;
		quit = true;
		cond.wakeOne();
		piecesWorking[pieceIndex]=false;
		gotPiece=false;
		return WRITE_OUT_OF_FILE;
	}
	else if(offset + pieceIndex * PieceSize == fileSize){
		int i, tmpSize = 0;
		piecesWorking[pieceIndex]=false;
		for(i=pieceIndex;i<pieceNum-1;i++){
			if(piecesDone[i] || piecesWorking[i]){
				tmpSize -= PieceSize;
			}
			piecesDone[i]=true;
		}
		if(piecesDone[i] || piecesWorking[i]){ 
			tmpSize -= (fileSize - PieceSize * (pieceNum - 1));
		}
		piecesDone[i]=true;
		tmpSize += (fileSize - (pieceIndex * PieceSize));
		finishedSize += tmpSize;
		progressForwarded = true;
		cond.wakeOne();
		gotPiece=false;
		return WRITE_OK;
	}
	else if(pieceIndex < pieceNum -1 && 
		offset >= PieceSize)
	{
		piecesWorking[pieceIndex]=false;
		progressForwarded = true;
		while(offset >= PieceSize){
			if(!piecesWorking[pieceIndex] &&
					!piecesDone[pieceIndex]){
				finishedSize += PieceSize;
			}
			piecesDone[pieceIndex]=true;
			offset -= PieceSize;
			pieceIndex ++;
		}
		cond.wakeOne();
		if(piecesWorking[pieceIndex]||piecesDone[pieceIndex]){
			gotPiece=false;
		}
		else{
			piecesWorking[pieceIndex]=true;
		}
		return WRITE_OK;
	}
	else {
		cond.wakeOne();
		return WRITE_OK;
	}
}

QByteArray FileManager::readBlock(int pieceIndex, qint64 offset, int length){
	return QByteArray();
}

bool FileManager::writeBlock(int pieceIndex, qint64 offset, const QByteArray &data){
	if(!file.isOpen()){
		if(!file.open(QIODevice::ReadWrite)){
			exit(1);
		}
	}
	qint64 totalOffset = (qint64)pieceIndex * PieceSize + offset;
	file.seek(totalOffset);
	if(file.write(data) < 0){
		exit(1);
	}
	return true;
}

int FileManager::getPieceNum(){
	return pieceNum;
}

int FileManager::getUndonePieceNum(){
	return piecesDone.count(false);
}

int FileManager::getWorkingPieceNum(){
	return piecesWorking.count(true);
}

void FileManager::remove(){
	if(file.exists()){
		file.remove();
	}
	if(digestFile.exists()){
		digestFile.remove();
	}

}

bool FileManager::isAcceptRange(){
	return acceptRange;
}

void FileManager::setAcceptRange(){
	acceptRange = true;
}

// search for the best piece to give,
// mask it as working then return the pieceIndex
int FileManager::getPiece(int formerPieceIndex){
	QMutexLocker locker(&mutex);
	if(formerPieceIndex >= 0){
		piecesWorking[formerPieceIndex]=false;
	// the next piece is good for a link
		if(formerPieceIndex < pieceNum -1 && 
			!piecesWorking[formerPieceIndex+1] && 
		!piecesDone[formerPieceIndex+1])
		{
			piecesWorking[formerPieceIndex+1]=true;
			return formerPieceIndex+1;
		}
	}
	// now we should search for the longest piece
	int pIndex=0, pLength=0, i;
	int pShuttle=0, ShuttleLength=0;

	while (pieceNum > pShuttle && 
			( piecesWorking[pShuttle] || piecesDone[pShuttle]))
		pShuttle++;
	if(pShuttle == pieceNum)
		return -1;

	ShuttleLength=0;
	for(i=pShuttle ;i<pieceNum && !piecesDone[i] 
		&& !piecesWorking[i]; i++)
	{
		ShuttleLength++;
	}
	while(pShuttle < pieceNum){
		if(pShuttle>=1 && piecesWorking[pShuttle-1]){
			if(ShuttleLength /2 +1 > pLength){
			pLength = ShuttleLength /2 + 1;
		pIndex = pShuttle + ShuttleLength /2;
		}
	}
	else{
		if(ShuttleLength > pLength){
			pLength = ShuttleLength;
		pIndex = pShuttle;
		}
	}
	pShuttle += (ShuttleLength+1);

	ShuttleLength=0;
   	while(pShuttle < pieceNum && 
			(piecesWorking[pShuttle] || piecesDone[pShuttle]) )
	{
		pShuttle++;
	}
	for(i=pShuttle ;i<pieceNum && piecesDone[i]==false 
			&& piecesWorking[i]==false; i++)
		{
		ShuttleLength++;
		}
	}
	piecesWorking[pIndex]=true;
	return pIndex;
}

void FileManager::cleanUp(){
	file.close();
	digestFile.close();
}

void FileManager::releasePiece(int pieceIndex){
	QMutexLocker locker(&mutex);
	piecesWorking[pieceIndex] = false;
}


/* void FileManager::setPieceDone(int pieceIndex){
	QMutexLocker locker(&mutex);
	piecesDone[pieceIndex] = true;
	cond.wakeOne();
} */

void FileManager::run(){
	// a uninitialized file. wait for it to be initialized.
	quit=false;
	if(newFile) { 
		mutex.lock();
		cond.wait(&mutex);
		mutex.unlock();
	}
	do {
		// Go to sleep if there's nothing to do.
		mutex.lock();
		if(getUndonePieceNum()==0){
			digestFile.remove();
			file.close();
			quit = true;
			emit finished();
			mutex.unlock();
			break;
		}
		if(!quit && readRequests.isEmpty() && writeRequests.isEmpty() ){
			cond.wait(&mutex);
		} 
		mutex.unlock();
		// Read pending read requests
		mutex.lock();
		QList<ReadRequest> newReadRequests = readRequests;
		readRequests.clear();
		mutex.unlock();
		while (!newReadRequests.isEmpty()) {
			ReadRequest request = newReadRequests.takeFirst();
			QByteArray block = readBlock(request.pieceIndex,
				request.offset, request.length);
			//emit dataRead(request.id, request.pieceIndex,
			//request.offset, block);
		}
	
		// Write pending write requests
		mutex.lock();
		QList<WriteRequest> newWriteRequests = writeRequests;
		writeRequests.clear();
		while (!quit && !newWriteRequests.isEmpty()) {
			WriteRequest request = newWriteRequests.takeFirst();
			writeBlock(request.pieceIndex, request.offset,
					request.data);
		}
		if(progressForwarded){
			emit progressForward();
			progressForwarded = false;
			file.flush();
			syncDigest();
		}
		mutex.unlock();
	} while (!quit );
};

void FileManager::syncDigest(){
	if(!digestFile.isOpen()){
		if(!digestFile.open(QIODevice::Truncate|QIODevice::WriteOnly)){
			exit(1);
		}
	}
	QDataStream digestData(&digestFile);
	digestData << acceptRange
		   << fileSize 
		   << finishedSize
		   << qint32(pieceNum) 
		   << dateString
		   << piecesDone ;
	digestFile.close();	   
}

bool FileManager::loadDigest(){
	if(!digestFile.isOpen()){
		if(!digestFile.open(QIODevice::ReadOnly)){
			exit(1);
		}
	}
	QDataStream digestData(&digestFile);
	digestData >> acceptRange
		   >> fileSize 
		   >> finishedSize
		   >> pieceNum 
		   >> dateString
		   >> piecesDone ;
	digestFile.close();
	if(digestData.status() != QDataStream::Ok){
		fileSize = -1;
		return false;
	}
	piecesWorking.resize(pieceNum);
	return true;
}
